/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.loaders;
import java.awt.Image;
import java.beans.BeanInfo;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;
import java.util.Enumeration;
import org.openide.*;
import org.openide.cookies.InstanceCookie;
import org.openide.filesystems.*;
import org.openide.loaders.*;
import org.openide.actions.OpenAction;
import org.openide.util.*;
import org.openide.util.actions.*;
import org.openide.nodes.*;
/** A data object whose only purpose is to supply <code>InstanceCookie</code>.
* The instances are created by default instantiation; the name of the class
* to instantiate is stored on disk, typically right in the file name.
* <p>This data object is generally used to configure menus and toolbars,
* though it could be used in any situation requiring instances to be present in
* a folder; for example, anything using {@link FolderInstance}.
* <p>Use {@link #create} and {@link #remove} to make the objects.
*
* @author Ian Formanek
*/
public class InstanceDataObject extends MultiDataObject implements InstanceCookie {
/** generated Serialized Version UID */
static final long serialVersionUID = -6134784731744777123L;
private static final String INSTANCE_ICON_BASE =
"/org/openide/resources/instanceObject"; // NOI18N
/** opening symbol */
private static final char OPEN = '[';
/** closing symbol */
private static final char CLOSE = ']';
/** File extension for instance data objects. */
public static final String INSTANCE = "instance"; // NOI18N
/** Create a new instance.
* Do not use this to make instances; use {@link #create}.
* @param pf primary file object for this data object
* @param loader the loader
* @throws DataObjectExistsException if it already exists
*/
public InstanceDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException {
super(pf, loader);
}
/** Finds instance of specified name in a given folder.
* @param folder the folder to create the instance data object in
* @param name the name to give to the object (can be <code>null</code> if no special name besides the class name is needed)
* @param className the name of the class the new object should provide an instance of
* @return the found instance data object or null if it does not exist
*/
public static InstanceDataObject find (DataFolder folder, String name, String className) {
FileObject fo = folder.getPrimaryFile ();
String fileName;
if (name == null) {
fileName = className.replace ('.', '-');
} else {
fileName = escape (name) + OPEN + className.replace ('.', '-') + CLOSE;
}
FileObject file = fo.getFileObject (fileName, INSTANCE);
if (file != null) {
try {
return (InstanceDataObject)DataObject.find (file);
} catch (DataObjectNotFoundException e) {
return null;
}
} else {
return null;
}
}
/** Finds instance of specified name in a given folder.
* @param folder the folder to create the instance data object in
* @param name the name to give to the object (can be <code>null</code> if no special name besides the class name is needed)
* @param clazz the class to create instance for
* @return the found instance data object or null if it does not exist
*/
public static InstanceDataObject find (DataFolder folder, String name, Class clazz) {
return find (folder, name, clazz.getName ());
}
/** Create a new <code>InstanceDataObject</code> in a given folder. If object with specified name already exists, it is returned.
* You should specify the name if there is a chance another file of the same
* instance class already exists in the folder; or just to provide a more
* descriptive name, which will appear in the Explorer for example.
* @param folder the folder to create the instance data object in
* @param name the name to give to the object (can be <code>null</code> if no special name besides the class name is needed)
* @param className the name of the class the new object should provide an instance of
* @return the newly created or eisting instance data object
* @exception IOException if the file cannot be created
*/
public static InstanceDataObject create (DataFolder folder, String name, String className) throws IOException {
FileObject fo = folder.getPrimaryFile ();
String fileName;
if (name == null) {
fileName = className.replace ('.', '-');
} else {
fileName = escape (name) + OPEN + className.replace ('.', '-') + CLOSE;
}
FileObject newFile = fo.getFileObject (fileName, INSTANCE);
if (newFile == null) newFile = fo.createData (fileName, INSTANCE);
return (InstanceDataObject)DataObject.find (newFile);
}
/** Create a new <code>InstanceDataObject</code> in a given folder. If object with specified name already exists, it is returned.
* You should specify the name if there is a chance another file of the same
* instance class already exists in the folder; or just to provide a more
* descriptive name, which will appear in the Explorer for example.
* @param folder the folder to create the instance data object in
* @param name the name to give to the object (can be <code>null</code> if no special name besides the class name is needed)
* @param clazz the class to create instance for
* @return the newly created or eisting instance data object
* @exception IOException if the file cannot be created
*/
public static InstanceDataObject create (DataFolder folder, String name, Class clazz) throws IOException {
return create (folder, name, clazz.getName ());
}
/** Remove an existing instance data object.
* If you have the exact file name, just call {@link DataObject#delete};
* this method lets you delete an instance you do not have an exact record
* of the file name for, based on the same information used to create it.
* @param folder the folder to remove the file from
* @param name the name of the instance (can be <code>null</code>)
* @param className the name of class the object referred to
* @return <code>true</code> if the instance was succesfully removed, <code>false</code> if not
*/
public static boolean remove (DataFolder folder, String name,
String className) {
FileLock lock = null;
try {
String fileName;
if (name == null) {
fileName = className.replace ('.', '-');
} else {
fileName = escape (name) + OPEN + className.replace ('.', '-') + CLOSE;
}
FileObject fileToRemove =
folder.getPrimaryFile().getFileObject (fileName, INSTANCE);
if (fileToRemove == null) // file not found
return false;
lock = fileToRemove.lock();
fileToRemove.delete(lock);
} catch (IOException exc) {
// something is bad, instance wasn't removed
return false;
} finally {
if (lock != null)
lock.releaseLock();
}
return true;
}
/** Remove an existing instance data object.
* If you have the exact file name, just call {@link DataObject#delete};
* this method lets you delete an instance you do not have an exact record
* of the file name for, based on the same information used to create it.
* @param folder the folder to remove the file from
* @param name the name of the instance (can be <code>null</code>)
* @param className the name of class the object referred to
* @return <code>true</code> if the instance was succesfully removed, <code>false</code> if not
*/
public static boolean remove (DataFolder folder, String name, Class clazz) {
return remove (folder, name, clazz.getName ());
}
/* Help context for this object.
* @return help context
*/
public HelpCtx getHelpCtx () {
HelpCtx test = InstanceSupport.findHelp (this);
if (test != null)
return test;
else
return new HelpCtx (InstanceDataObject.class);
}
/* Provides node that should represent this data object. When a node for representation
* in a parent is requested by a call to getNode (parent) it is the exact copy of this node
* with only parent changed. This implementation creates instance
* <CODE>DataNode</CODE>.
* <P>
* This method is called only once.
*
* @return the node representation for this data object
* @see DataNode
*/
protected Node createNodeDelegate () {
return new InstanceNode ();
}
/* The name of the bean for this file or null if the class name is not encoded
* in the file name and rather the CLASS_NAME property from the file content should be used.
*
* @return the name for the instance or null if the class name is not defined in the name
*/
public String instanceName () {
String name = getPrimaryFile ().getName ();
int first = name.indexOf (OPEN) + 1;
int last = name.indexOf (CLOSE);
if (last < 0) {
last = name.length ();
}
// take only a part of the string
if (first < last) {
name = name.substring (first, last);
}
name = name.replace ('-', '.');
//System.out.println ("Original: " + getPrimaryFile ().getName () + " new one: " + name); // NOI18N
name = org.openide.util.Utilities.translate(name);
return name;
}
/* The class of the instance represented by this cookie.
* Can be used to test whether the instance is of valid
* class before it is created.
*
* @return the class of the instance
* @exception IOException an I/O error occured
* @exception ClassNotFoundException the class has not been found
*/
public Class instanceClass ()
throws java.io.IOException, ClassNotFoundException {
String className = instanceName ();
return Class.forName (
className, true, TopManager.getDefault ().currentClassLoader ()
);
}
/*
* @return an object to work with
* @exception IOException an I/O error occured
* @exception ClassNotFoundException the class has not been found
*/
public Object instanceCreate ()
throws java.io.IOException, ClassNotFoundException {
try {
Class c = instanceClass ();
if (SharedClassObject.class.isAssignableFrom (c)) {
// special support
return SharedClassObject.findObject (c, true);
} else {
// create new instance
return c.newInstance ();
}
} catch (ClassNotFoundException ex) {
throw ex;
} catch (Throwable ex) {
if (ex instanceof ThreadDeath) {
throw (ThreadDeath)ex;
}
if (System.getProperty ("netbeans.debug.exceptions") != null) ex.printStackTrace ();
throw new ClassNotFoundException (ex.getMessage ());
}
}
/* Overriden to return only first part till the bracket */
public String getName () {
String superName = super.getName();
int bracket = superName.indexOf (OPEN);
return unescape ((bracket < 0) ? superName : superName.substring (0, bracket));
}
/** Hex-escapes anything potentially nasty in some text. */
private static String escape (String text) {
int len = text.length ();
StringBuffer escaped = new StringBuffer (len);
for (int i = 0; i < len; i++) {
char c = text.charAt (i);
// For some reason Windoze throws IOException if angle brackets in filename...
if (c == '/' || c == ':' || c == '\\' || c == OPEN || c == CLOSE || c == '<' || c == '>' ||
c == '.' || c == '"' || c < '\u0020' || c > '\u007E' || c == '#') {
// Hex escape.
escaped.append ('#');
String hex = Integer.toString (c, 16).toUpperCase ();
if (hex.length () < 4) escaped.append ('0');
if (hex.length () < 3) escaped.append ('0');
if (hex.length () < 2) escaped.append ('0');
escaped.append (hex);
} else {
escaped.append (c);
}
}
return escaped.toString ();
}
/** Removes hex escapes and regenerates displayable Unicode. */
private static String unescape (String text) {
int len = text.length ();
StringBuffer unesc = new StringBuffer (len);
for (int i = 0; i < len; i++) {
char c = text.charAt (i);
if (c == '#') {
if (i + 4 >= len) {
if (Boolean.getBoolean ("netbeans.debug.exceptions")) // NOI18N
System.err.println("trailing garbage in instance name: " + text); // NOI18N
break;
}
try {
char[] hex = new char[4];
text.getChars (i + 1, i + 5, hex, 0);
unesc.append ((char) Integer.parseInt (new String (hex), 16));
} catch (NumberFormatException nfe) {
if (Boolean.getBoolean ("netbeans.debug.exceptions")) // NOI18N
nfe.printStackTrace ();
}
i += 4;
} else {
unesc.append (c);
}
}
return unesc.toString ();
}
//
// Temporary properties methods
//
/** Provides access to properties defined inside the instance file.
* @param key name of the key for which the value is to be acquired
* @return the value for specified key or null if the key was not found
*/
private Properties instanceProperties;
String getProperty (String key) {
return getInstanceProperties ().getProperty (key);
}
/** Lazy initialization of instance Properties object */
private Properties getInstanceProperties () {
if (instanceProperties == null) {
instanceProperties = new Properties ();
if (getPrimaryFile ().getSize () == 0) {
// empty file need not be loaded
return instanceProperties;
}
try {
java.io.InputStream is = getPrimaryFile ().getInputStream ();
try {
instanceProperties.load (is);
} finally {
is.close ();
}
} catch (java.io.FileNotFoundException e) {
// OK, empty properties
} catch (java.io.IOException e) {
// OK, empty properties
}
}
return instanceProperties;
}
static final String ICON_NAME = "icon"; // NOI18N
static final String translateIcon(String name) {
final int namelen = name.length();
final int ext = name.lastIndexOf('.');
String tmp = name.substring(1, ext);
tmp = tmp.replace('/', '.');
tmp = org.openide.util.Utilities.translate(tmp);
tmp = tmp.replace('.', '/');
tmp = '/' + tmp + name.substring(ext);
return tmp;
}
/** Node that uses special ways to obtain icon.
*/
private final class InstanceNode extends DataNode
implements Runnable {
/** icon from bean info */
private Image beanInfoIcon;
/** Constructor */
public InstanceNode () {
super (InstanceDataObject.this, Children.LEAF);
setIconBase(INSTANCE_ICON_BASE);
setDefaultAction (SystemAction.get (OpenAction.class));
RequestProcessor.postRequest (this);
}
/** Find an icon for this node (in the closed state).
* @param type constant from {@link java.beans.BeanInfo}
* @return icon to use to represent the node
*/
public Image getIcon (int type) {
if (beanInfoIcon != null) {
return beanInfoIcon;
} else {
return super.getIcon (type);
}
}
/** Find an icon for this node (in the open state).
* This icon is used when the node may have children and is expanded.
*
* @param type constant from {@link java.beans.BeanInfo}
* @return icon to use to represent the node when open
*/
public Image getOpenedIcon (int type) {
return getIcon (type);
}
/** Runs the get for the icon */
public void run () {
String expliciteIcon = getProperty (ICON_NAME);
if (expliciteIcon != null) {
expliciteIcon = translateIcon(expliciteIcon);
// create the icon from the resource
java.net.URL url = TopManager.getDefault ().currentClassLoader ().getResource (expliciteIcon);
if (url != null) {
javax.swing.ImageIcon imic = new javax.swing.ImageIcon (url);
// only take the icon if it is correctly loaded
if (imic.getImageLoadStatus() == java.awt.MediaTracker.COMPLETE) {
beanInfoIcon = imic.getImage ();
}
}
} else {
try {
BeanInfo bi = Utilities.getBeanInfo (instanceClass ());
if (bi != null) {
beanInfoIcon = bi.getIcon (BeanInfo.ICON_COLOR_16x16);
}
} catch (java.io.IOException e) {
// Problem ==>> use default icon
} catch (ClassNotFoundException e2) {
// Problem ==>> use default icon
} catch (java.beans.IntrospectionException e3) {
// Problem ==>> use default icon
}
}
if (beanInfoIcon != null) {
// fire change
fireIconChange ();
}
}
}
}
/*
* Log
* 26 Gandalf 1.25 1/17/00 Jesse Glick Angle brackets must be
* escaped on Windows.
* 25 Gandalf 1.24 1/15/00 Jesse Glick Zero-padding.
* 24 Gandalf 1.23 1/15/00 Jesse Glick IDO now privately
* manages all filename escaping for safety.
* 23 Gandalf 1.22 1/15/00 Jesse Glick Recognizing hex escapes
* in instance names.
* 22 Gandalf 1.21 1/13/00 Ian Formanek NOI18N
* 21 Gandalf 1.20 1/12/00 Ian Formanek NOI18N
* 20 Gandalf 1.19 1/11/00 Jesse Glick Was not creating the
* correct InstanceCookie for its own instances.
* 19 Gandalf 1.18 12/29/99 Jaroslav Tulach Special handling for
* SharedClassObject.
* 18 Gandalf 1.17 11/3/99 Jaroslav Tulach Runs iconization later.
* 17 Gandalf 1.16 10/25/99 Jaroslav Tulach Runs instantiation
* later.
* 16 Gandalf 1.15 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 15 Gandalf 1.14 8/17/99 Ian Formanek Undone last change
* 14 Gandalf 1.13 8/17/99 Ian Formanek Generated serial version
* UID
* 13 Gandalf 1.12 8/9/99 Ian Formanek Survives more problems
* when creating instance
* 12 Gandalf 1.11 7/25/99 Ian Formanek Exceptions printed to
* console only on "netbeans.debug.exceptions" flag
* 11 Gandalf 1.10 7/15/99 Ian Formanek create methods return
* survive if instance of specified name already exists (and return it),
* added find methods
* 10 Gandalf 1.9 7/11/99 David Simonek window system change...
* 9 Gandalf 1.8 6/25/99 Jesse Glick Instances can have
* sensible help contexts.
* 8 Gandalf 1.7 6/24/99 Jesse Glick Gosh-honest HelpID's.
* 7 Gandalf 1.6 6/10/99 Jaroslav Tulach
* 6 Gandalf 1.5 6/10/99 Jesse Glick [JavaDoc]
* 5 Gandalf 1.4 6/9/99 Ian Formanek Fixed resources for
* package change
* 4 Gandalf 1.3 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 3 Gandalf 1.2 6/1/99 David Simonek method remove(...) added
* 2 Gandalf 1.1 5/14/99 Jaroslav Tulach Pallete works again.
* 1 Gandalf 1.0 5/11/99 Jaroslav Tulach
* $
*/